﻿using System;
using System.Net;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Chisel.Core
{
	/// <summary>
	/// The relative position of an ActorControls to its actor.
	/// </summary>
	public enum ControlLocation { North, South, East, West, Center };

	/// <summary>
	/// A set of controls that wraps an actor.
	/// </summary>
	public class ControlsWrapper : Wrapper
	{

		/// <summary>
		/// Adds itself to the actor's diagram canvas.
		/// </summary>
		/// <param name="actor">The actor to wrap.</param>
		public ControlsWrapper(Actor actor) : base(actor)
		{
			_stack = new StackPanel() {
				Orientation = Orientation.Horizontal
			};
			_stack.MouseEnter += new MouseEventHandler(OnStackMouseEnter);
			LocalCanvas.Children.Add(_stack);

			// make the local canvas accept mouse events
			//LocalCanvas.Background = new SolidColorBrush(Colors.Red);
			//IsHitTestVisible = true;

			// set up the fade animation
			VisibleOpacity = 0.8;
			FadeDuration = TimeSpan.FromMilliseconds(200);
			_fadeAnimation = new DoubleAnimation() {
				Duration = new Duration(FadeDuration),
				From = 0,
				To = VisibleOpacity
			};
			_fadeStoryBoard = new Storyboard();
			_fadeStoryBoard.Children.Add(_fadeAnimation);
			Storyboard.SetTarget(_fadeAnimation, this);
			Storyboard.SetTargetProperty(_fadeAnimation, new PropertyPath(OpacityProperty));

			Opacity = 0;

			_timer = new DispatcherTimer() {
				Interval = FadeDuration
			};
			_timer.Tick += new EventHandler(OnTimerTick);
		}

		/// <summary>
		/// The stack panel used to layout the controls.
		/// </summary>
		private StackPanel _stack;

		private ControlLocation _location;
		/// <summary>
		/// The relative position of the controls to its actor.
		/// </summary>
		public ControlLocation Location
		{ 
			get { return _location; }
			set
			{
				_location = value;

				// determine what orientation the stack should have
				if (_location == ControlLocation.East || _location == ControlLocation.West)
					_stack.Orientation = Orientation.Vertical;
				else
					_stack.Orientation = Orientation.Horizontal;

				ComputeLayout();
			}
		}


		#region Layout
		
		protected override void ComputeLayout()
		{
			_stack.UpdateLayout();

			double pad = 0; // padding around the edge

			var actorPos = Actor.Position;
			double stackWidth = Double.IsNaN(_stack.ActualWidth) ? 0 : _stack.ActualWidth;
			double stackHeight = Double.IsNaN(_stack.ActualHeight) ? 0 : _stack.ActualHeight;
			switch (Location)
			{
				case ControlLocation.North:
					MoveTo(actorPos.X - pad, actorPos.Y - stackHeight - pad);
					Width = Math.Max(Actor.Width + 2*pad, stackWidth + 2*pad);
					Height = Actor.Height + stackHeight;
					Canvas.SetLeft(_stack, 0);
					Canvas.SetTop(_stack, 0);
					break;

				case ControlLocation.South:
					MoveTo(actorPos.X, actorPos.Y);
					Width = Math.Max(Actor.Width + 2 * pad, stackWidth + 2 * pad);
					Height = Actor.Height + stackHeight;
					Canvas.SetLeft(_stack, 0);
					Canvas.SetTop(_stack, Actor.Height);
					break;

				case ControlLocation.East:
					MoveTo(actorPos.X - pad, actorPos.Y - pad);
					Width = Actor.Width + stackWidth + 2*pad;
					Height = Actor.Height + 2*pad;
					Canvas.SetLeft(_stack, Actor.Width);
					Canvas.SetTop(_stack, 0);
					break;

				case ControlLocation.West:
					MoveTo(actorPos.X - stackWidth - pad, actorPos.Y - pad);
					Width = Actor.Width + stackWidth + 2 * pad;
					Height = Actor.Height + 2 * pad;
					Canvas.SetLeft(_stack, 0);
					Canvas.SetTop(_stack, 0);
					break;

				case ControlLocation.Center:
					double w = Math.Max(stackWidth + 2 * pad, Actor.ActualWidth + 2 * pad);
					Width = w;
					double h = Math.Max(stackHeight + 2 * pad, Actor.ActualHeight + 2 * pad);
					Height = h;
					var center = Actor.Center;
					MoveTo(center.X - w/2, center.Y - h/2);
					Canvas.SetLeft(_stack, (w - stackWidth) / 2);
					Canvas.SetTop(_stack, (h - stackHeight) / 2);
					break;
			}
		}

		#endregion


		#region Animations

		/// <summary>
		/// True if the controls are active and visible to the user.
		/// </summary>
		public bool IsActive { get; private set; }

		/// <summary>
		/// The opacity when visible.
		/// </summary>
		public double VisibleOpacity { get; set; }

		/// <summary>
		/// Duration of fade events (in milliseconds).
		/// </summary>
		public TimeSpan FadeDuration { get; set; }

		private DoubleAnimation _fadeAnimation;

		private Storyboard _fadeStoryBoard;

		/// <summary>
		/// Fades the controls into visibility.
		/// </summary>
		protected void FadeIn()
		{
			IsActive = true;
			//IsHitTestVisible = false;
			ComputeLayout();
			_fadeAnimation.Duration = FadeDuration;
			_fadeAnimation.From = 0;
			_fadeAnimation.To = VisibleOpacity;
			_fadeStoryBoard.Begin();
		}

		/// <summary>
		/// Fades the controls out of visibility.
		/// </summary>
		protected void FadeOut()
		{
			IsActive = false;
			//IsHitTestVisible = true;
			_fadeAnimation.Duration = FadeDuration;
			_fadeAnimation.From = Opacity;
			_fadeAnimation.To = 0;
			_fadeStoryBoard.Begin();
		}

		#endregion


		#region Mouse Interaction

		/// <summary>
		/// Timer used to allow transitions between the actor and controls without fading out.
		/// </summary>
		private DispatcherTimer _timer;

		/// <summary>
		/// This gets set to true when the cursor leaves the controls or the actor and is waiting to enter one again before fade out.
		/// </summary>
		private bool _waitingForEnter = false;

		/// <summary>
		/// Handles to timer tick to see the user has entered the control stack.
		/// </summary>
		private void OnTimerTick(object sender, EventArgs e)
		{
			if (_waitingForEnter) // still waiting, didn't enter anything
				FadeOut();
			_timer.Stop();
		}

		protected override void OnMouseEnter(MouseEventArgs e)
		{
			base.OnMouseEnter(e);
			if (!IsActive)
				FadeIn();
			Actor.OnRemoteMouseEnter(e);
		}

		protected override void OnActorMouseEnter(object sender, MouseEventArgs e)
		{
			base.OnActorMouseEnter(sender, e);
			if (!IsActive)
				FadeIn();
			_waitingForEnter = false;
		}

		/// <summary>
		/// Handles the stack's mouse enter event.
		/// </summary>
		/// <remarks>Unless this happens within a certain amount of time, we consider that we left.</remarks>
		private void OnStackMouseEnter(object sender, MouseEventArgs e)
		{
			//if (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - _leftTime > 500)
			//    FadeOut();
			_waitingForEnter = false;
		}

		protected override void OnMouseLeave(MouseEventArgs e)
		{
			base.OnMouseLeave(e);
			_waitingForEnter = true;
			_timer.Start();
			Actor.OnRemoteMouseLeave(e);
		}

		protected override void OnActorMouseLeave(object sender, MouseEventArgs e)
		{
			base.OnActorMouseLeave(sender, e);
			_waitingForEnter = true;
			_timer.Start();
		}

		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonDown(e);
			if (!e.Handled)
				Actor.OnRemoteMouseLeftButtonDown(e);
		}

		protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonUp(e);
			if (!e.Handled)
				Actor.OnRemoteMouseLeftButtonUp(e);
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);
			Actor.OnRemoteMouseMove(e);
		}

		#endregion


		#region Controls

		private List<Control> _controls = new List<Control>();

		/// <summary>
		/// Adds a control.
		/// </summary>
		public void AddControl(Control control)
		{
			_controls.Add(control);
			_stack.Children.Add(control);
			control.IsTabStop = false;
		}

		/// <summary>
		/// Removes a control.
		/// </summary>
		public void RemoveControl(Control control)
		{
			_controls.Remove(control);
			_stack.Children.Remove(control);
		}

		#endregion

	}
}
